package ceura.ia.practica.android.ui;

import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.GridLayout;
import android.widget.Toast;
import ceura.ia.practica.AlgoritmoAStar;
import ceura.ia.practica.Laberinto;
import ceura.ia.practica.Nodo;
import ceura.ia.practica.Point;
import ceura.ia.practica.Situacion;
import ceura.ia.practica.android.R;
import ceura.ia.practica.android.brush.BackgroundType;
import ceura.ia.practica.android.brush.Brush;
import ceura.ia.practica.android.listeners.LaberintoTouchListener;
import ceura.ia.practica.android.utilities.LaberintoUtils;
import ceura.ia.practica.heuristica.HeuristicaManhattan;
import ceura.ia.practica.listener.AStarListener;
import ceura.ia.practica.properties.Propiedades;
import java.util.ArrayList;

/**
 * Abstracción del framelayout de Android que contiene las vistas hijas, tanto
 * la barra de herramietas como el gridlayout ( el laberinto ), la vista se
 * infla y se devuelve por composición en vez de herencia.
 *
 * @author 66785403 (Jorge Madrid Portillo)
 */
public class LaberintoView implements AStarListener {

    private static final int LABERINTO_VIEW_ID = R.layout.main;
    private LayoutInflater inflater;
    private Propiedades properties;
    private Context context;
    private GridLayout gridLayout;
    private FrameLayout frameLayout;
    private LaberintoTouchListener laberintoTouchListener;
    private Brush brush;
    private BarraSuperior barraSuperior;
    private boolean selectionMode = true;
    private Celda startCelda = null, endCelda = null;
    private Laberinto laberinto;
    private LaberintoUtils laberintoUtils;
    private AlgoritmoAStar algoritmoCalculator;
    private Handler laberintoHandler;
    private long sleepTime = 30;

    /**
     *
     * @param context Contexto Android.
     */
    public LaberintoView(Context context) {
        this.context = context;
        inflater = LayoutInflater.from(context);
        properties = Propiedades.getInstance();
        laberintoUtils = new LaberintoUtils();
        brush = Brush.WATER;
        algoritmoCalculator = new AlgoritmoAStar(new HeuristicaManhattan());
        laberintoHandler = new Handler();
        algoritmoCalculator.addListener(this);
    }

    /**
     *
     * @param context Contexto Android.
     * @return El frame Layout del laberinto.
     */
    public View getNewLaberintoView(Context context) {
        this.context = context;
        return this.getNewLaberintoView();
    }

    /**
     * Esté metodo se encarga de inflar las vistas y preparar los listeners asi
     * como otras operaciones de iniciazión.
     *
     * @return El framelayout del laberinto.
     */
    public View getNewLaberintoView() {
        frameLayout = (FrameLayout) inflater.inflate(LABERINTO_VIEW_ID, null);
        gridLayout = (GridLayout) frameLayout.findViewById(R.id.gridview);

        barraSuperior = new BarraSuperior(context, this);

        gridLayout.setColumnCount(properties.numCol);
        gridLayout.setRowCount(properties.numRow);

        fillGrid();

        initListeners();

        return frameLayout;
    }

    private void fillGrid() {
        for (int i = 0; i < properties.getNumCeldas(); i++) {
            Celda celda = new Celda(context, i);
            gridLayout.addView(celda, i);
        }
    }

    private void initListeners() {
        laberintoTouchListener = new LaberintoTouchListener(this);
        gridLayout.setOnTouchListener(laberintoTouchListener);
        gridLayout.setOnClickListener(laberintoTouchListener);
    }

    /**
     * Prepara un modelo de datos en base a los valores de la vista y se lo
     * manda al calculador para que realize la busqueda.
     */
    public void findPath() {
        if (!validateLaberinto()) {
            return;
        }
        final int n = gridLayout.getColumnCount();
        final int m = gridLayout.getRowCount();
        Point startPoint = laberintoUtils.parsearTablatoPoint(startCelda.getPosition(), n, m);
        Point endPoint = laberintoUtils.parsearTablatoPoint(endCelda.getPosition(), n, m);
        laberinto = new Laberinto(n, m, new Situacion(startPoint.x, startPoint.y), endPoint);

        for (int i = 0, len = gridLayout.getChildCount(); i < len; i++) {
            Celda celda = (Celda) gridLayout.getChildAt(i);
            if (celda.getBackgroundType() == BackgroundType.WATER) {
                laberinto.setAgua(laberintoUtils.parsearTablatoPoint(i, n, m));
            }
        }

        barraSuperior.toggle();
        new Thread(new Runnable() {
            public void run() {
                algoritmoCalculator.calcularAlgoritmo(laberinto, sleepTime);
            }
        }).start();
    }

    private boolean validateLaberinto() {
        return startCelda != null && endCelda != null;
    }

    /**
     *
     * @return La barra superior de herramientas.
     */
    public BarraSuperior getBarraSuperior() {
        return barraSuperior;
    }

    /**
     *
     * @return El gridlayout que representa el laberinto.
     */
    public GridLayout getGridLayout() {
        return gridLayout;
    }

    /**
     *
     * @param position La posición (x,y)
     * @return La celda que ocupa dicha posición.
     */
    public Celda getCeldaAtPosition(int position) {
        return (Celda) gridLayout.getChildAt(position);
    }

    /**
     *
     * @return El pincel que pintará el laberinto.
     */
    public Brush getBrush() {
        return brush;
    }

    /**
     *
     * @param brush El pincel que pintará el laberinto.
     */
    public void setBrush(Brush brush) {
        this.brush = brush;
    }

    /**
     *
     * @param id El id de la vista.
     * @return La vista hija con dicho id.
     */
    public View findViewById(int id) {
        return frameLayout.findViewById(id);
    }

    /**
     *
     * @param selectionMode El modo de selección de esta vista.
     */
    public void setSelectionMode(boolean selectionMode) {
        this.selectionMode = selectionMode;
    }

    /**
     *
     * @return Si está en modo selección.
     */
    public boolean isSelectionMode() {
        return selectionMode;
    }

    /**
     *
     * @param endCelda La celda de destino del laberinto.
     */
    public void setEndCelda(Celda endCelda) {
        if (this.endCelda != null) {
            this.endCelda.setBackGroundType(BackgroundType.GRASS);
        }
        this.endCelda = endCelda;
    }

    /**
     *
     * @return La celda de destino del laberinto.
     */
    public Celda getEndCelda() {
        return endCelda;
    }

    /**
     *
     * @param startCelda La celda de inicio del laberinto.
     */
    public void setStartCelda(Celda startCelda) {
        if (this.startCelda != null) {
            this.startCelda.setBackGroundType(BackgroundType.GRASS);
        }
        this.startCelda = startCelda;
    }

    /**
     *
     * @return La celda de origen del laberinto.
     */
    public Celda getStartCelda() {
        return startCelda;
    }

    /**
     *
     * @return El frameLayout o la vista padre.
     */
    public FrameLayout getFrameLayout() {
        return frameLayout;
    }

    /**
     * Éste método se llama cuando el algorimto de cálculo del camino más corto
     * ha terminado, ya que estamos subscritos al sistema de listeners del
     * calculador.
     *
     * @param caminoOptimo El camino óptimo.
     */
    public void finishAlgorithm(final ArrayList<Nodo> caminoOptimo) {
        laberintoHandler.post(new Runnable() {
            public void run() {
                paintCaminoOptimo(caminoOptimo);
                Toast.makeText(context, "Terminado!", Toast.LENGTH_SHORT).show();
                barraSuperior.toggle();
            }
        });
    }

    private void paintCaminoOptimo(ArrayList<Nodo> caminoOptimo) {
        for (Nodo nodo : caminoOptimo) {
            Point point = new Point(nodo.x, nodo.y);
            Celda celda = (Celda) gridLayout.getChildAt(laberintoUtils.parsearTablaGrid(point));
            celda.setBackgroundColor(Color.BLACK);
        }
    }

    /**
     * Notificación de final de una iteración del calculador de laberintos, nos
     * permite ir pintando los nodos descubiertos en tiempo real para ver como
     * evoluciona el algoritmo.
     *
     * @param descubiertos Los nodos descubiertos.
     * @param nodo El nodo final.
     */
    public void finishIteration(ArrayList<Nodo> descubiertos, final Nodo nodo) {
        laberintoHandler.post(new Runnable() {
            public void run() {
                Celda celda = LaberintoView.this.getCeldaAtPosition(laberintoUtils.
                        parsearTablaGrid(new Point(nodo.x, nodo.y)));
                celda.setBackGroundType(BackgroundType.CAMINO);
            }
        });
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException ex) {
        }
    }

    /**
     *
     * @param sleepTime El tiempo de espera.
     */
    public void setSleepTime(long sleepTime) {
        this.sleepTime = sleepTime;
    }
}
